﻿/// relevant links
/// OneNote xml, API: http://msdn.microsoft.com/en-us/library/ms788684(v=office.12).aspx

using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml.Linq;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Windows.Ink;
using System.Windows.Input;
using System.IO;
using System.Threading;
using OneNote = Microsoft.Office.Interop.OneNote;

namespace ESJImporter
{
    class OneNoteFunctions
    {
        public enum OneNoteTypes { unknown = -1, Notebook = 0, Section, SectionGroup, Page, SubPage };

        /// <summary>
        /// read the hierarchy information from OneNote
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static public void readONHierarchy(TreeView tv)
        {
            tv.Nodes.Clear();
            string notebookXml;
            OneNote.Application onenoteApp = new OneNote.Application();

            onenoteApp.GetHierarchy(null, OneNote.HierarchyScope.hsPages, out notebookXml);

            var doc = XDocument.Parse(notebookXml);
            var ns = doc.Root.Name.Namespace;

            // loop through the individual notebooks
            foreach (var notebookNode in from node in doc.Descendants(ns + "Notebook") select node)
            {
                // loop through hierarchies
                TreeNode tn_notebook = tv.Nodes.Add(notebookNode.Attribute("ID").Value, notebookNode.Attribute("name").Value);

                foreach (XElement subNode in notebookNode.Nodes())
                {
                    TreeNode addnodes = readSubNodes(subNode);
                    if (addnodes!=null) tn_notebook.Nodes.Add(addnodes);
                }

                /*
                // look through the sections
                TreeNode tn_notebook = tv.Nodes.Add(notebookNode.Attribute("ID").Value, notebookNode.Attribute("name").Value);
                foreach (var sectionNode in from node in notebookNode.Descendants(ns + "Section") select node)
                {
                    // loop through the pages
                    TreeNode tn_section = tn_notebook.Nodes.Add(sectionNode.Attribute("ID").Value, sectionNode.Attribute("name").Value);
                    foreach (var pageNode in from node in sectionNode.Descendants(ns + "Page") select node)
                    {
                        tn_section.Nodes.Add(pageNode.Attribute("ID").Value, pageNode.Attribute("name").Value);
                    }
                }
                 * */
            }
        }

        static private TreeNode readSubNodes(XElement onenoteelems)
        {
            //MessageBox.Show(onenoteelems.ToString());
            if ((onenoteelems.Attribute("name") == null) || (onenoteelems.Attribute("ID") == null)) return null;
            
            TreeNode result = new TreeNode(onenoteelems.Attribute("name").Value);
            result.Name = onenoteelems.Attribute("ID").Value;
// todo: consider hiding subpages
            if (GetTypeFromXML(onenoteelems) == OneNoteTypes.SubPage) result.Text = "   " + result.Text;

            if (GetTypeFromXML(onenoteelems) == OneNoteTypes.unknown) return null;
            if (!((GetTypeFromXML(onenoteelems) == OneNoteTypes.Page) || (GetTypeFromXML(onenoteelems) == OneNoteTypes.SubPage)))
            {
                foreach (XElement subNode in onenoteelems.Nodes())
                {
                    TreeNode addnodes = readSubNodes(subNode);
                    if (addnodes != null) result.Nodes.Add(addnodes);
                }
            }
            return result;
        }

        static public void InterpretStrokes()
        {
            // relevant links: 
            // http://www.johandekoning.nl/index.php/2009/12/08/top2onenote-addin-making-my-digital-notepad-notes-searchable/
            // http://blogs.microsoft.co.il/blogs/tamir/archive/2006/12.aspx
            InkAnalyzer myanalyzer = new InkAnalyzer();
        }

        /// <summary>
        /// build onenote XML page with page ID pgid from the stroke collection provided
        /// </summary>
        /// <param name="pgID"></param>
        /// <param name="strokes"></param>
        /// <returns></returns>
        static public XElement buildOneNoteXML(string pgID, StrokeCollection strokes, Preferences settings)
        {
            // create XML
            XNamespace oneNamespace = "http://schemas.microsoft.com/office/onenote/2007/onenote";
            XElement serialDocument = new XElement(oneNamespace + "Page", new XAttribute(XNamespace.Xmlns + "one", oneNamespace));

            serialDocument.Add(new XAttribute("ID", pgID));

            if (strokes.Count() > 0)
            {
                XElement outlineNode = new XElement(oneNamespace + "InkDrawing");       // object ID?
                serialDocument.Add(outlineNode);

                XElement positionNode = new XElement(oneNamespace + "Position");
                positionNode.Add(new XAttribute("x", strokes.GetBounds().Left + settings.xOffset));  // min x
                positionNode.Add(new XAttribute("y", strokes.GetBounds().Top + settings.yOffset)); // min y
                positionNode.Add(new XAttribute("z", 0)); // incremental
                outlineNode.Add(positionNode);

                XElement sizeNode = new XElement(oneNamespace + "Size");
                sizeNode.Add(new XAttribute("width", strokes.GetBounds().Width));     // max x - min x
                sizeNode.Add(new XAttribute("height", strokes.GetBounds().Height));    // max y - min y
                outlineNode.Add(sizeNode);


                XElement dataNode = new XElement(oneNamespace + "Data");
                outlineNode.Add(dataNode);

                using (MemoryStream ms = new MemoryStream())
                {
                    strokes.Save(ms);
                    byte[] isfBytes = ms.ToArray();
                    dataNode.SetValue(Convert.ToBase64String(isfBytes));
                }
            }
            //tb_Serialized.AppendText(System.Environment.NewLine);
            //tb_Serialized.AppendText(outlineNode.ToString());
            return serialDocument;
        }

        /// <summary>
        /// create a section and return it's id
        /// if the section already exists, it can be overwritten by setting "clearifexists" to true
        /// </summary>
        /// <param name="notebookid"></param>
        /// <param name="sectionname"></param>
        /// <param name="clearifexists"></param>
        /// <returns></returns>
        static public string CreateSection(string notebookid, string sectionname, bool clearifexists)
        {
            OneNote.Application onenoteApp = new OneNote.Application();

            // get notebook
            string selectednotebook;
            onenoteApp.GetHierarchy(notebookid, OneNote.HierarchyScope.hsPages, out selectednotebook);
            //onenoteApp.GetPageContent(notebookid, out selectednotebook, Microsoft.Office.Interop.OneNote.PageInfo.piAll);
            XDocument xdoc = XDocument.Parse(selectednotebook);
            XNamespace ns = xdoc.Root.Name.Namespace;

            // get the path and notebook name
            // <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2007/onenote" name="..." nickname="..." ID="{...}" path="..." lastModifiedTime="..." color="..." />
            string notebookpath = xdoc.Root.Attribute("path").Value;
            string notebookname = xdoc.Root.Attribute("name").Value;
            string sectionID = "";

            // check if section exists already
            foreach (var sectionNode in from node in xdoc.Descendants(ns + "Section") select node)
            {
                if (sectionNode.Attribute("name").Value == sectionname) // found section with same name
                {
                    if (clearifexists)  // this can go up
                        onenoteApp.DeleteHierarchy( sectionNode.Attribute("ID").Value, new DateTime() );
                }
            }

// TODO: more fine-granulated control over pages (section delete), e.g. only delete page_x in section etc.

            // create section
            onenoteApp.OpenHierarchy(notebookpath + "\\" + sectionname + ".one", System.String.Empty, out sectionID, OneNote.CreateFileType.cftSection);
            
            return sectionID;
        }

        /// <summary>
        /// create a page in the specified section with the given name
        /// if "clearifexists" is set to true, pages with the same name AND ALL ITS SUBPAGES will be deleted prior to writing
        /// </summary>
        /// <param name="sectionID"></param>
        /// <param name="pageName"></param>
        /// <param name="clearifexists"></param>
        /// <param name="sectionName"></param>
        /// <returns></returns>
        static public string CreatePage(string sectionID, string pageName, bool clearifexists)
        {
            OneNote.Application onenoteApp = new OneNote.Application();
            string strPageID = "";
            string selectedsection;
            onenoteApp.GetHierarchy(sectionID, OneNote.HierarchyScope.hsPages, out selectedsection);
            XDocument xsec = XDocument.Parse(selectedsection);
            XNamespace ns = xsec.Root.Name.Namespace;

            // check if page with same name exists already
            if (clearifexists)
            {
                IEnumerable<XElement> pagedescendants = xsec.Descendants(ns + "Page");
                for (int n=0; n<pagedescendants.Count(); n++)
                {
                    if (pagedescendants.ElementAt(n).Attribute("name").Value.ToLower() == pageName.ToLower()) //.Contains(sectionName.ToLower())) 
                    {
                        // detect subpages: ALL follow pages with "isSubPage=true"
                        for (int m = n+1; m < pagedescendants.Count(); m++)
                        {
                            if (GetTypeFromXML(pagedescendants.ElementAt(m)) != OneNoteTypes.SubPage) break;
                            onenoteApp.DeleteHierarchy(pagedescendants.ElementAt(m).Attribute("ID").Value, new DateTime());
                        }
                        onenoteApp.DeleteHierarchy(pagedescendants.ElementAt(n).Attribute("ID").Value, new DateTime());
                        break;
                    }
                }
            }
            try
            {
                onenoteApp.CreateNewPage(sectionID, out strPageID, Microsoft.Office.Interop.OneNote.NewPageStyle.npsDefault);
            }
            catch (System.Runtime.InteropServices.COMException comex)
            {
                if (comex.ErrorCode == unchecked((int)0x8004200b))
                {
                    MessageBox.Show("There was a read access error to the destination page - please select another destination");
                    return "";
                }
            }

            /// set base information of new page
            XNamespace oneNamespace = "http://schemas.microsoft.com/office/onenote/2007/onenote";
            XElement serialDocument = new XElement(oneNamespace + "Page", new XAttribute(XNamespace.Xmlns + "one", oneNamespace));
            serialDocument.Add(new XAttribute("ID", strPageID));
            serialDocument.Add(new XAttribute("name", pageName));
            XElement titleNode = new XElement(oneNamespace + "Title");
            serialDocument.Add(titleNode);
            XElement infoNode = new XElement(oneNamespace + "OE");
            titleNode.Add(infoNode);
            infoNode.Add(new XAttribute("author", "OneNoteSyncer"));
            infoNode.Add(new XAttribute("creationTime", DateTime.Now.ToString("o")));
            XElement tnameNode = new XElement(oneNamespace + "T");
            infoNode.Add(tnameNode);
            tnameNode.Add(new XCData(pageName));

            onenoteApp.UpdatePageContent(serialDocument.ToString(), DateTime.MinValue);

            return strPageID;
        }

        /// <summary>
        /// turn a normal page into a subpage
        /// todo: just on basis of id
        /// </summary>
        /// <param name="xPage"></param>
        static public void MakePageSub(XElement xPage)
        {
            xPage.Add(new XAttribute("isSubPage", "true"));
            OneNote.Application onenoteApp = new OneNote.Application();
            onenoteApp.UpdateHierarchy(xPage.ToString());
        }
        
        // TODO: GET PATH OF ONENOTE
        /// <summary>
        /// get the path AND name of a OneNote instance (notebook / section / page )
        /// in the form of notebook:\section\...\page
        /// </summary>
        /// <param name="ID"></param>
        /// <returns></returns>
        static public string GetFullName(string ID)
        {
            OneNote.Application onenoteApp = new OneNote.Application();
            string fullname = "";

            string notebookXml;
            onenoteApp.GetHierarchy(null, OneNote.HierarchyScope.hsPages, out notebookXml);
            var fullhierarchy = XDocument.Parse(notebookXml);
            var ns = fullhierarchy.Root.Name.Namespace;

            XElement page = findEntry(fullhierarchy.Root, ID);
            if (page == null) return "";

            fullname = page.Attribute("name").Value;
            while (page.Parent != null)
            {
                page = page.Parent;
                if (page.Attribute("name")!=null)
                    fullname = page.Attribute("name").Value + "\\" + fullname;
            }
            int firstslash=fullname.IndexOf("\\");
            fullname=fullname.Insert(firstslash, ":");
            return fullname;
        }

        static private XElement findEntry(XElement hierarchy, string id)
        {
            foreach (XElement xelem in hierarchy.Elements())
            {
                if (xelem.Attribute("ID")!=null)
                {
                    if (xelem.Attribute("ID").Value == id)
                    {
                        return xelem;
                    }
                }
                if (xelem.HasElements)
                {
                    XElement result = findEntry(xelem, id);
                    if (result != null) return result;
                }
            }
            return null;
        }

        static public string GetName(string ID)
        {
            OneNote.Application onenoteApp = new OneNote.Application();
            string XMLPageContent;
            onenoteApp.GetPageContent(ID, out XMLPageContent, Microsoft.Office.Interop.OneNote.PageInfo.piAll);
            XElement xpage = XElement.Parse(XMLPageContent);
            return xpage.Attribute("name").Value;
        }

        /// <summary>
        /// returns the type (notebook, section, page) of the OneNote instance
        /// notebook, sectiongroup, section, page, (issubpage=true)
        /// note: this is slow, so only use when absolutely necessary
        /// </summary>
        /// <param name="ID"></param>
        /// <returns></returns>
        static public OneNoteTypes GetType(string ID)
        {
            OneNote.Application onenoteApp = new OneNote.Application();
            string XMLPageContent;
            onenoteApp.GetPageContent(ID, out XMLPageContent, Microsoft.Office.Interop.OneNote.PageInfo.piAll);
            XElement xpage = XElement.Parse(XMLPageContent);

            return GetTypeFromXML(xpage);
        }

        static public OneNoteTypes GetTypeFromXML(XElement xpage)
        {
            switch (xpage.Name.LocalName.ToLower())
            {
                case "notebook":
                    return OneNoteTypes.Notebook;
                case "sectiongroup":
                    return OneNoteTypes.SectionGroup;
                case "section":
                    return OneNoteTypes.Section;
                case "page":
                    if (xpage.Attribute("isSubPage") != null)
                        if (xpage.Attribute("isSubPage").Value.ToLower() == "true")
                            return OneNoteTypes.SubPage;
                    return OneNoteTypes.Page;
            }
            return OneNoteTypes.unknown;
        }

        /// <summary>
        /// deletes the CONTENT of a page (nothing else)
        /// </summary>
        /// <param name="onenoteid"></param>
        static public void ClearPage(string pageID)
        {
            OneNote.Application onenoteApp = new OneNote.Application();
            string XMLPageContent;
            XElement xpage;
            onenoteApp.GetPageContent(pageID, out XMLPageContent, Microsoft.Office.Interop.OneNote.PageInfo.piAll);
            xpage = XElement.Parse(XMLPageContent);

            // only children are 
            //<one:Page>
            //  <one:PageSettings>
            //    <one:PageSize>
            //      <one:Automatic />
            //    </one:PageSize>
            //    <one:RuleLines/>
            //  </one:PageSettings>
            //  <one:Title>
            //    <one:OE>
            //      <one:T><![CDATA[tab2_1]]></one:T>
            //    </one:OE>
            //  </one:Title>

            // delete nodes
            foreach (XElement contentnode in xpage.Nodes())
            {
                if (((contentnode.Name.LocalName != "PageSettings")) &&
                    ((contentnode.Name.LocalName != "Title")))
                {
                    onenoteApp.DeletePageContent(pageID, contentnode.Attribute("objectID").Value, DateTime.MinValue);
                }
            }
        }

        static public void AddText(string pageID, double x, double y, string text, System.Drawing.Font textformat)
        {
            //<one:Outline>
            //    <one:Position x="36.0" y="86.4000015258789" z="0" />
            //    <one:Size width="442.201171875" height="116.2304611206055" />
            //    <one:OEChildren>
            //      <one:OE creationTime="2011-03-29T20:29:33.000Z" lastModifiedTime="2011-03-29T20:29:35.000Z" objectID="{5F66E677-8E99-46DD-B65F-AFAADAF9B651}{18}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt">
            //        <one:T><![CDATA[This is a text]]></one:T>
            //      </one:OE>
            //      <one:OE creationTime="2011-03-29T20:29:37.000Z" lastModifiedTime="2011-03-29T20:29:40.000Z" objectID="{5F66E677-8E99-46DD-B65F-AFAADAF9B651}{21}{B0}" alignment="left" style="font-family:Calibri;font-size:16.0pt;color:#17365D">
            //        <one:T><![CDATA[<span style='font-weight:bold'>This is header 1</span>]]></one:T>
            //      </one:OE>
            //      <one:OE creationTime="2011-03-29T20:29:41.000Z" lastModifiedTime="2011-03-29T20:29:44.000Z" objectID="{5F66E677-8E99-46DD-B65F-AFAADAF9B651}{23}{B0}" alignment="left" style="font-family:Calibri;font-size:13.0pt;color:#366092">
            //        <one:T><![CDATA[<span style='font-weight:bold'>This is header 2</span>]]></one:T>
            //      </one:OE>
            //      <one:OE creationTime="2011-03-29T20:29:49.000Z" lastModifiedTime="2011-03-29T20:29:53.000Z" objectID="{5F66E677-8E99-46DD-B65F-AFAADAF9B651}{25}{B0}" alignment="left" style="font-family:Calibri;font-size:17.0pt">
            //        <one:T><![CDATA[And this is a page title]]></one:T>
            //      </one:OE>
            //      <one:OE creationTime="2011-03-29T20:30:09.000Z" lastModifiedTime="2011-03-29T20:30:26.000Z" objectID="{5F66E677-8E99-46DD-B65F-AFAADAF9B651}{27}{B0}" alignment="left" style="font-family:'Times New Roman';font-size:30.0pt">
            //        <one:T><![CDATA[This is Times New Roman (30pt)]]></one:T>
            //      </one:OE>
            //      <one:OE creationTime="2011-03-29T20:30:28.000Z" lastModifiedTime="2011-03-29T20:30:31.000Z" objectID="{5F66E677-8E99-46DD-B65F-AFAADAF9B651}{29}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt">
            //        <one:T><![CDATA[And this is normal again]]></one:T>
            //      </one:OE>
            //    </one:OEChildren>
            //  </one:Outline>

            // create XML
            XNamespace oneNamespace = "http://schemas.microsoft.com/office/onenote/2007/onenote";
            XElement serialDocument = new XElement(oneNamespace + "Page", new XAttribute(XNamespace.Xmlns + "one", oneNamespace));
            serialDocument.Add(new XAttribute("ID", pageID));

            XElement outlineNode = new XElement(oneNamespace + "Outline"); 
            serialDocument.Add(outlineNode);

            XElement positionNode = new XElement(oneNamespace + "Position");
            positionNode.Add(new XAttribute("x", x));
            positionNode.Add(new XAttribute("y", y));
            positionNode.Add(new XAttribute("z", 0)); // incremental
            outlineNode.Add(positionNode);

            // size ??
            XElement OEChildrenNode = new XElement(oneNamespace + "OEChildren");
            outlineNode.Add(OEChildrenNode);

            XElement OENode = new XElement(oneNamespace + "OE");
            OEChildrenNode.Add(OENode);
            OENode.Add(new XAttribute("alignment", "left"));
            string textformatstring = "font-family:" + textformat.FontFamily.Name + ";font-size:" + textformat.SizeInPoints.ToString() + "pt";
            OENode.Add(new XAttribute("style", textformatstring)); 

            XElement textNode = new XElement(oneNamespace + "T");
            OENode.Add(textNode);
            string formatstring = "<span style='";
            // style='font-weight:bold;text-decoration:underline'
            if (textformat.Bold)
            {
                formatstring = formatstring + "font-weight:bold;";
            }
            if (textformat.Underline)
            {
                formatstring = formatstring + "text-decoration:underline;";
            }
            formatstring += "'>";
            textNode.Add(new XCData(formatstring + text + "</span>"));

            OneNote.Application onenoteApp = new OneNote.Application();
            onenoteApp.UpdatePageContent(serialDocument.ToString(), DateTime.MinValue);

        }
    }
}


